/***********************************************************************
 qttimecode.m - Adds a timecode track to a QuickTime video file.

 Created 2006.06.21 by Warren Young; based on example code in

	http://developer.apple.com/samplecode/qttimecode/

 Copyright (c) 2006 by Pixel Corps.  This program may be used under
 the terms of the X11 license, a copy of which should have accompanied
 this program, in the LICENSE file.
***********************************************************************/

#include "qttimecode.h"

#include <QTKit/QTKit.h>


////////////////////////////////////////////////////////////////////////
// Constants

// Height of the frame containing timecode data.  Width will be same as
// that of the video frames.
static const int kTimecodeTrackHeight = 50;


//// CStrToPascal255 ///////////////////////////////////////////////////
// Converts a C string to a 1-byte length-prefixed Pascal string.

static void
CStrToPascal255(char* ppstr, const char* pcstr)
{
	size_t len = strlen(pcstr);
	*ppstr = (len < 256 ? len : 255);
	memcpy(ppstr + 1, pcstr, *ppstr);
}


//// GetVideoFrameDimensions ///////////////////////////////////////////
// Returns the dimensions of the video track's frames, so we can size
// the timecode track accordingly.

static BOOL
GetVideoFrameDimensions(QTMovie* movie, Track* pVideoTrack,
		int* pw, int* ph)
{
	// Find first track holding video info
	*pVideoTrack = GetMovieIndTrackType([movie quickTimeMovie], 1,
			VisualMediaCharacteristic, movieTrackMediaType);
	if (*pVideoTrack) {
		// Return that track's frame dimensions
		Fixed w, h;
		GetTrackDimensions(*pVideoTrack, &w, &h);
		NSLog(@"Retrieved video track dimensions: %d x %d\n", w, h);
		*pw = w; *ph = h;
		return YES;
	}
	else {
		return NO;
	}
}


//// CreateTimecodeTrack ///////////////////////////////////////////////
// Creates the actual timecode track object.

static BOOL
CreateTimecodeTrack(QTMovie* movie, int frameWidth, QTTrack** ppTrack,
		QTMedia** ppMedia, MediaHandler* pHandler)
{
	NSError* qtError;
	Track track = NewMovieTrack([movie quickTimeMovie], frameWidth,
			kTimecodeTrackHeight, kNoVolume);
	if (track) {
		Media media;
		*ppTrack = [QTTrack trackWithQuickTimeTrack:track
				error:&qtError];
		media = NewTrackMedia(track, TimeCodeMediaType,
				GetMovieTimeScale([movie quickTimeMovie]), NULL, 0);
		if (media) {
			*ppMedia = [QTMedia mediaWithQuickTimeMedia:media
					error:&qtError];
			*pHandler = GetMediaHandler([*ppMedia quickTimeMedia]);
			if (*pHandler) {
				return YES;
			}
			else {
				NSLog(@"Failed to retrieve handler for QuickTime "
						"timecode media!\n");
				return NO;
			}
		}
		else {
			NSLog(@"Failed to create QuickTime media for timecode "
					"track: %@\n", [qtError localizedDescription]);
			return NO;
		}
	}
	else {
		NSLog(@"Failed to create QuickTime timecode track: %@\n",
				[qtError localizedDescription]);
		return NO;
	}
}


//// SetupTimecodeTrack ////////////////////////////////////////////////
// Sets up the parameters of the timecode track.

static BOOL
SetupTimecodeTrack(QTTrack* track, MediaHandler handler,
		int frameHeight, TimeCodeDef* pTCDef)
{
	MatrixRecord matrix;

	// Set up timecode definition: 30 fps, maximum 24 hours
	pTCDef->flags = tc24HourMax;
	pTCDef->fTimeScale = 600;
	pTCDef->frameDuration = 20;
	pTCDef->numFrames = 30;
	
	// Position timecode frame below video content
	GetTrackMatrix([track quickTimeTrack], &matrix);
	TranslateMatrix(&matrix, 0, frameHeight);
	SetTrackMatrix([track quickTimeTrack], &matrix);	
	
	// Make timecode track visible
	SetTrackEnabled([track quickTimeTrack], YES);
	TCSetTimeCodeFlags(handler, tcdfShowTimeCode, tcdfShowTimeCode);
	
	return YES;
}


//// GetVideoTrackDuration /////////////////////////////////////////////
// Grab video track's duration.  We don't need to do any conversion in
// here because the timecode track is created with the same time base
// as the video track.  If they diverge, this will break.

static TimeValue
GetVideoTrackDuration(QTMovie* movie)
{
	return GetMovieDuration([movie quickTimeMovie]);
}


//// AddTimeCodeSample /////////////////////////////////////////////////
// Adds a media sample to the timecode track.  We add just one, with the
// time being zero, which causes QuickTime to simply count up from there
// through the end of the video.

static BOOL
AddTimeCodeSample(QTMedia* media, TimeValue duration, TimeCodeDef tcDef,
		MediaHandler handler)
{
	OSErr err;
	BOOL bSuccess = NO;
	Handle frameHandle = 0;
	TimeCodeDescriptionHandle tcDesc = 0;
	
	if ((err = BeginMediaEdits([media quickTimeMedia])) == noErr) {
		long tcSize = sizeof(TimeCodeDescription);
		tcDesc = (TimeCodeDescriptionHandle)NewHandleClear(tcSize);
		if (tcDesc) {
			UserData userData;
	
			// Create a handle object to store a frame count
			frameHandle = NewHandle(sizeof(SInt32));
			if (!frameHandle) {
				NSLog(@"Failed to create frame handle!!\n");
				goto bail;
			}
			
			// Set up timecode description record
			(**tcDesc).descSize = tcSize;
			(**tcDesc).dataFormat = TimeCodeMediaType;
			(**tcDesc).timeCodeDef = tcDef;
			
			if ((err = NewUserData(&userData)) == noErr) {
				// Store the media name info in the timecode sample
				UserData userData;
				char sourceName[256];
				Handle nameHandle = 0;
				
				CStrToPascal255(sourceName, "?? the source name ??");
				if ((PtrToHand(sourceName + 1, &nameHandle,
						sourceName[0]) == noErr) &&
						(AddUserDataText(userData, nameHandle,
						TCSourceRefNameType, 1, langEnglish) == 
						noErr)) {
					TCSetSourceRef(handler, tcDesc, userData);
				}

				// Clean up
				if (nameHandle) {
					DisposeHandle(nameHandle);
				}
				DisposeUserData(userData);
			}

			//// Add starting time sample to timecode track
			// Create timecode record, all zeroes, meaning that this
			// is the start of the movie.
			TimeCodeRecord tcRec;
			memset(&tcRec, 0, sizeof(TimeCodeRecord));

			// Convert timecode record info to a frame number, in
			// big-endian byte order.
			TCTimeCodeToFrameNumber(handler,
					&(**tcDesc).timeCodeDef, &tcRec,
					(long*)*frameHandle);
			**frameHandle = EndianS32_NtoB(**frameHandle);

			// Finally, add the timecode sample
			if (AddMediaSample([media quickTimeMedia], frameHandle, 0,
					GetHandleSize(frameHandle), duration,
					(SampleDescriptionHandle)tcDesc, 1, 0, 0) ==
					noErr) {
				bSuccess = YES;
			}
			else {
				NSLog(@"Failed to add timecode sample!\n");
			}
		}
	}
	
bail:
	if (tcDesc) {
		DisposeHandle((Handle)tcDesc);
	}
		
	if (frameHandle) {
		DisposeHandle(frameHandle);
	}

	return (EndMediaEdits([media quickTimeMedia]) == noErr) && bSuccess;
}


//// FinalizeTimecodeTrack /////////////////////////////////////////////
// Insert the timecode media into the timecode track, and tie the
// timecode track to the video track.

static BOOL
FinalizeTimecodeTrack(QTTrack* tcTrack, Track videoTrack,
		TimeValue duration)
{
	return (InsertMediaIntoTrack([tcTrack quickTimeTrack], 0, 0,
			duration, fixed1) == noErr) && (AddTrackReference(
			videoTrack, [tcTrack quickTimeTrack], TimeCodeMediaType,
			NULL) == noErr);
}


//// AddTimecodeTrackToMovie ///////////////////////////////////////////
// Adds a timecode track to the given QT movie.  This is based loosely
// on QTTC_AddTimeCodeToMovie() in the sample referenced above.

BOOL
AddTimecodeTrackToMovie(QTMovie* movie)
{
	int frameWidth, frameHeight;
	QTTrack* tcTrack;
	Track videoTrack;
	QTMedia* tcMedia;
	MediaHandler handler;
	TimeCodeDef tcDef;

	TimeValue duration = GetVideoTrackDuration(movie);
	NSLog(@"Movie duration: %d time units\n", duration);

	return GetVideoFrameDimensions(movie, &videoTrack, &frameWidth,
				&frameHeight) &&
			CreateTimecodeTrack(movie, frameWidth, &tcTrack, &tcMedia,
				&handler) &&
			SetupTimecodeTrack(tcTrack, handler, frameHeight, &tcDef) &&
			AddTimeCodeSample(tcMedia, duration, tcDef, handler) &&
			FinalizeTimecodeTrack(tcTrack, videoTrack, duration);
}
